package com.powernode.ssm.service;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.powernode.ssm.pojo.po.UserLoginToken;
import com.powernode.ssm.service.UserLoginTokenService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * Redis 服务高可用代理，支持降级容灾：
 * - 正常情况：走 Redis 操作
 * - Redis异常时：降级处理（如不存 token、不限制频控、记录日志等）
 * - 支持定时检测 Redis 是否恢复
 * - 降级时可选使用数据库存储 token（通过 UserLoginTokenService）
 */
@Slf4j
@Service
public class RedisServiceWithFallback extends RedisService {

    // 真正的 RedisService 实现（即你原来注入的 RedisService，基于 StringRedisTemplate）
    @Autowired
    @Qualifier("redisService") // 与你原来的 RedisService bean 名称保持一致
    private RedisService redisService; // 原始实现，我们要代理它

    // 可选：用于降级存储 token 到数据库
    @Autowired
    private UserLoginTokenService userLoginTokenService;

    // Redis 是否健康的标志
    private final AtomicBoolean redisHealthy = new AtomicBoolean(true);

    // JSON 工具（如果原 RedisService 中有，也可直接复用它的 objectMapper）
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Bean 初始化
     */
    @PostConstruct
    public void init() {
        log.info("[Redis容灾] RedisServiceWithFallback 初始化完成");
    }

    /**
     * 允许外部设置 Redis 健康状态（可用于手动控制或管理接口）
     */
    public void setRedisHealthy(boolean healthy) {
        boolean old = this.redisHealthy.getAndSet(healthy);
        if (healthy && !old) {
            log.info("[Redis容灾] Redis 状态变更为：可用");
        } else if (!healthy && old) {
            log.warn("[Redis容灾] Redis 状态变更为：不可用，将启用降级策略");
        }
    }

    /**
     * 定时任务：每 60 秒检测一次 Redis 是否恢复健康
     */
    @Scheduled(fixedRate = 60000) // 每分钟检测一次
    public void checkRedisHealth() {
        if (!redisHealthy.get()) {
            try {
                // 简单探测：尝试设置一个临时 key 并读取
                String testKey = "redis_health_check:" + System.currentTimeMillis();
                boolean success = redisService.setObjectWithExpire(testKey, "1", 10, TimeUnit.SECONDS);
                if (success) {
                    // 再尝试获取
                    String val = redisService.getObject(testKey, String.class);
                    if ("1".equals(val)) {
                        log.info("[Redis容灾] Redis 健康检查成功，自动恢复连接");
                        setRedisHealthy(true);
                        // 清理测试 key（可选）
                        redisService.delete(testKey);
                        return;
                    }
                }
            } catch (Exception e) {
                log.warn("[Redis容灾] Redis 健康检查失败，继续保持降级状态", e);
            }
        }
    }

    /**
     * 判断当前 Redis 是否可用
     */
    public boolean isRedisAvailable() {
        return redisHealthy.get();
    }

    /**
     * 通用降级执行方法（带返回值）
     */
    private <T> T fallback(String operation, Supplier<T> redisCall, Supplier<T> fallbackCall) {
        if (!isRedisAvailable()) {
            log.warn("[Redis降级] Redis不可用，执行降级逻辑，操作：{}", operation);
            return fallbackCall.get();
        }
        try {
            return redisCall.get();
        } catch (Exception e) {
            log.error("[Redis容灾] Redis操作异常 [{}], 降级处理", operation, e);
            setRedisHealthy(false); // 出现异常，标记为不可用
            return fallbackCall.get(); // 执行降级逻辑
        }
    }

    /**
     * 通用降级执行方法（无返回值）
     */
    private void fallback(String operation, Runnable redisCall, Runnable fallbackCall) {
        if (!isRedisAvailable()) {
            log.warn("[Redis降级] Redis不可用，执行降级逻辑，操作：{}", operation);
            fallbackCall.run();
            return;
        }
        try {
            redisCall.run();
        } catch (Exception e) {
            log.error("[Redis容灾] Redis操作异常 [{}], 降级处理", operation, e);
            setRedisHealthy(false);
            fallbackCall.run();
        }
    }

    // ========== 下面是 RedisService 接口方法的代理实现 ==========

    @Override
    public void setObject(String key, Object value) {
        fallback(
                "setObject:" + key,
                () -> redisService.setObject(key, value),
                () -> {
                    log.warn("[降级] 不存储对象到 Redis，key={}", key);
                    // 降级：什么都不做，或者你可以存数据库
                }
        );
    }

    @Override
    public <T> T getObject(String key, Class<T> clazz) {
        return fallback(
                "getObject:" + key,
                () -> redisService.getObject(key, clazz),
                () -> {
                    log.warn("[降级] 未从 Redis 获取到对象，key={}", key);
                    return null; // 降级：返回 null，或查库
                }
        );
    }

    @Override
    public Boolean setObjectWithExpire(String key, Object value, long timeout, TimeUnit unit) {
        return fallback(
                "setObjectWithExpire:" + key,
                () -> redisService.setObjectWithExpire(key, value, timeout, unit),
                () -> {
                    log.warn("[降级] 不存储带过期时间的对象到 Redis，key={}", key);
                    // 降级：你可以选择存入数据库
                    return false;
                }
        );
    }

    @Override
    public void resetObjectWithExpire(String key, Object value, long timeout, TimeUnit unit) {
        fallback(
                "resetObjectWithExpire:" + key,
                () -> redisService.resetObjectWithExpire(key, value, timeout, unit),
                () -> {
                    log.warn("[降级] 不重置 Redis 中对象的过期时间，key={}", key);
                    // 降级：什么都不做
                }
        );
    }

    @Override
    public void delete(String key) {
        fallback(
                "delete:" + key,
                () -> redisService.delete(key),
                () -> {
                    log.warn("[降级] 不删除 Redis 中的 key，key={}", key);
                    // 降级：什么都不做
                }
        );
    }

    @Override
    public Long increment(String key) {
        return fallback(
                "increment:" + key,
                () -> redisService.increment(key),
                () -> {
                    log.warn("[降级] 登录频控计数未执行（Redis不可用），key={}, 降级返回允许（模拟成功）", key);
                    return 1L; // 模拟成功，避免登录被阻止
                }
        );
    }

    @Override
    public void expire(String key, long timeout, TimeUnit unit) {
        fallback(
                "expire:" + key,
                () -> redisService.expire(key, timeout, unit),
                () -> {
                    log.warn("[降级] 未设置 Redis key 过期时间，key={}", key);
                    // 降级：什么都不做
                }
        );
    }

    @Override
    public Boolean hasKey(String key) {
        return fallback(
                "hasKey:" + key,
                () -> redisService.hasKey(key),
                () -> {
                    log.warn("[降级] 未查询 Redis 中是否存在 key，key={}", key);
                    return false;
                }
        );
    }

    // ========== 可选增强：Token 降级存储到数据库 ==========

    /**
     * 【可选】降级时将 token 存入数据库（需 UserLoginTokenService 已实现）
     */
    public void saveTokenToDatabaseOnFallback(String userId, String token, long expireSeconds) {
        if (!isRedisAvailable()) {
            LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireSeconds);
            userLoginTokenService.saveOrUpdateToken(userId, token, expireTime);
            log.info("[Token降级存储] 用户 {} 的 token 已降级存储到数据库，过期时间：{}", userId, expireTime);
        }
    }

    /**
     * 【可选】从数据库查询 token（降级读取）
     */
    public Optional<String> getTokenFromDatabase(String userId) {
        if (!isRedisAvailable()) {
            Optional<UserLoginToken> tokenOpt = userLoginTokenService.getTokenByUserId(userId);
            return tokenOpt.map(UserLoginToken::getToken);
        }
        return Optional.empty();
    }
}